/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef CVML_BYTECODE_HPP
#define CVML_BYTECODE_HPP

struct ByteCode
{
    std::vector<Subroutine*> subs; 
    std::vector<Subroutine*> exported; 
	std::vector<std::string> constants; 

public:

    ByteCode() 
    {
    }

    ~ByteCode() {
        for (size_t i=0; i < subs.size(); ++i)
            delete(subs[i]);
    }

	int GetIndexOf(const std::string& s) {
        for (size_t i=0; i < subs.size(); ++i) {
            if (GetSub(i)->GetName() == s)
                return i;
        }
        return -1;
	}

	int GetIndexOf(Subroutine* sub) {
        for (size_t i=0; i < subs.size(); ++i) {
            if (GetSub(i) == sub)
                return i;
        }
        return -1;
	}

    Subroutine* GetSub(const std::string& s) {
		int n = GetIndexOf(s);
		if (n < 0) 
			return NULL;
        return GetSub(n);
    }

    Subroutine* GetSub(int n) {
        return subs[n];
    }

    void RemoveSub(int n) {
        size_t tmp = subs.size();
        subs.erase(subs.begin() + n);
        Assert(subs.size() < tmp);
    }

    int GetNumSubs() {
        return subs.size();
    }

	void SwapSubs(int i, int j) {
		std::swap(subs[i], subs[j]);
	}

	Subroutine* NewSub(const std::string& name) {
        Subroutine* r = new Subroutine(name);
        subs.push_back(r);
        return r;
    }

	Subroutine* NewSub() {
		Subroutine* r = new Subroutine("auto" + ToStr(GetNumSubs()));
		subs.push_back(r);
		return r;
	}

    Subroutine* NewExportedSub(const std::string& name) {
        Subroutine* r = NewSub(name);
        exported.push_back(r);
        return r;
    }
    
    void StripNoops() {
        for (int i=0; i < GetNumSubs(); ++i)
            GetSub(i)->StripNoops();
    }

	int GetNumConstants() {
		return constants.size();
	}

	const std::string& GetConstant(int n) {
		return constants[n];
	}

	int GetTotalSize() {
		int r = 0;
		for (int i=0; i < GetNumSubs(); ++i)
			r += GetSub(i)->GetTotalSize();
		return r;
	}

	int GetTotalConstantSize() {
		int r = 0;
		for (int i=0; i < GetNumConstants(); ++i) 
			r += GetConstant(i).size() + 1;
		return r;
	}

	int AddConstant(ConstString s) {
		int n = IndexOf(constants, s);
		if (n >= 0)
			return n;
		constants.push_back(s);
		return constants.size() - 1;
	}

	template<typename F>
	void WithEachSub(F& f) {
		for (int i=0; i < GetNumSubs(); ++i) 
			f(GetSub(i));
	}

	void ConvertToBinary(Binary& bin)
	{
		bin.fxn_indexes.Reallocate(GetNumSubs());
		bin.const_data.Reallocate(GetTotalConstantSize());
		bin.const_indexes.Reallocate(GetNumConstants());
		bin.code.Reallocate(GetTotalSize());

		// Copy the constant data 
		int cur = 0;
		for (int i=0; i < GetNumConstants(); ++i) {
			// update indexes
			bin.const_indexes[i] = cur;
			const std::string& s = GetConstant(i);
			for (size_t j=0; j < s.size(); ++j)
				bin.const_data[cur++] = s[j];
			bin.const_data[cur++] = '\0';
		}

		// Copy the code
		cur = 0; 
		for (int i=0; i < GetNumSubs(); ++i) {
			// update indexes
			bin.fxn_indexes[i] = cur;
			for (Instruction* j = GetSub(i)->first; j != NULL; j = j->next) {
				if (!j->IsNoop())
				{
					bin.code[cur++] = (u1)j->opcode;
					u4 operand = j->operand;
					switch (j->GetWidth()) {
						case (5):
							bin.code[cur++] = (operand & 0xFF000000) >> 24;
							bin.code[cur++] = (operand & 0xFF0000) >> 16;
						case (3):
							bin.code[cur++] = (operand & 0xFF00) >> 8;
						case (2):
							bin.code[cur++] = j->operand;
							break;
						case (1):
							// do nothing
							break;
						default:
							Error("illegal opcode width");
					}
				}
			}
		}
	}

	bool IsExported(Subroutine* sub) {
		return Contains(exported, sub);
	}
};

#endif
